package mysql

import (
	"database/sql"
	"errors"
	"github.com/gin-gonic/gin"
	"time"
	"xinduobang/stock/core"
	"xinduobang/stock/core/json"
	"xinduobang/stock/dao/mysql/sqlx"
	"xinduobang/stock/utils"
)

type Operate struct {
	ctx    *gin.Context
	dbName string
	db     *sql.DB
}

var (
	ERR_AFFECTED_NONE = errors.New("affected rows less 1")
)

func init() {
	SetTagName("db")
}

func NewOperate(c *gin.Context, dbName string) (*Operate, error) {
	op := &Operate{
		ctx:    c,
		dbName: dbName,
	}
	var err error
	op.db, err = core.GetMysql(dbName)
	if err != nil {
		utils.Error(c, "get mysql error:", err)
		return nil, err
	}

	return op, nil
}

// tx由参数传过去，这里不用关心
func NewOperateForTx(c *gin.Context, dbName string) *Operate {
	return &Operate{
		ctx:    c,
		dbName: dbName,
	}
}

func (op *Operate) write(sqlString string, valueArgs []interface{}) (affected int64, err error) {
	timeBegin := time.Now().UnixNano()
	defer func() {
		timeEnd := time.Now().UnixNano()
		timeCost := float64(timeEnd-timeBegin) / 1e9 * 1000
		if err != nil && !CheckErrAffectedNone(err) {
			utils.Errorf(op.ctx, "write_fail||sqlString=%v||valueArgs=%v||rowsAffected=%v||err=%v||proc_time=%v", sqlString, valueArgs, affected, err.Error(), timeCost)
		} else {
			utils.Infof(op.ctx, "write||sqlString=%v||valueArgs=%v||rowsAffected=%v||proc_time=%v", sqlString, valueArgs, affected, timeCost)
		}
	}()

	stmt, err := op.db.Prepare(sqlString)
	if err != nil {
		return affected, err
	}
	defer stmt.Close()

	res, err := stmt.Exec(valueArgs...)
	if err != nil {
		return affected, err
	}

	affected, err = res.RowsAffected()
	if err != nil {
		return affected, err
	}

	if affected < 1 {
		return affected, ERR_AFFECTED_NONE
	}

	return
}

func CheckErrAffectedNone(err error) bool {
	if err == ERR_AFFECTED_NONE {
		return true
	}
	return false
}

func CheckEmptyResult(err error) bool {
	if err == ErrEmptyResult {
		return true
	}
	return false
}

func (op *Operate) readAll(res interface{}, sqlString string, valueArgs []interface{}) (err error) {
	timeBegin := time.Now().UnixNano()
	defer func() {
		timeEnd := time.Now().UnixNano()
		timeCost := float64(timeEnd-timeBegin) / 1e9 * 1000
		if err != nil && !CheckEmptyResult(err) {
			utils.Errorf(op.ctx, "read_fail||sqlString=%v||valueArgs=%v||err=%v||proc_time=%v", sqlString, valueArgs, err.Error(), timeCost)
		} else {
			utils.Infof(op.ctx, "read||sqlString=%v||valueArgs=%v||proc_time=%v", sqlString, valueArgs, timeCost)
		}
	}()
	//timeEnd := time.Now().UnixNano()
	//timeCost2 := float64(timeEnd-timeBegin) / 1e9 * 1000
	//utils.Infof(op.ctx, "readAll 001 sqlString=%v||proc_time=%v", sqlString, timeCost2)
	rows, err := op.db.Query(sqlString, valueArgs...)
	if err != nil {
		return err
	}
	//timeEnd = time.Now().UnixNano()
	//timeCost2 = float64(timeEnd-timeBegin) / 1e9 * 1000
	//utils.Infof(op.ctx, "readAll 002 sqlString=%v||proc_time=%v", sqlString, timeCost2)

	defer func() {
		err1 := rows.Close()
		if err1 != nil {
			// 只记录不阻塞
			utils.Error(op.ctx, "rows close error:", err1)
		}
	}()
	//timeEnd = time.Now().UnixNano()
	//timeCost2 = float64(timeEnd-timeBegin) / 1e9 * 1000
	//utils.Infof(op.ctx, "readAll 003 sqlString=%v||proc_time=%v", sqlString, timeCost2)

	//err = Scan(rows, res) // 原来滴滴的写法，有一些bug
	err = sqlx.StructScan(rows, res)
	if err != nil {
		return err
	}
	//timeEnd = time.Now().UnixNano()
	//timeCost2 = float64(timeEnd-timeBegin) / 1e9 * 1000
	//utils.Infof(op.ctx, "readAll 004 sqlString=%v||proc_time=%v", sqlString, timeCost2)

	return
}

func (op *Operate) readRow(res interface{}, sqlString string, valueArgs []interface{}) (err error) {
	timeBegin := time.Now().UnixNano()
	defer func() {
		timeEnd := time.Now().UnixNano()
		timeCost := float64(timeEnd-timeBegin) / 1e9 * 1000
		valueArgsByte, err1 := json.Marshal(valueArgs)
		if err1 != nil {
			utils.Error(op.ctx, "json.Marshal error:", err1)
		}
		if err != nil && !CheckEmptyResult(err) {
			utils.Errorf(op.ctx, "read_row_fail||sqlString=%v||valueArgs=%v||err=%v||proc_time=%v", sqlString, string(valueArgsByte), err.Error(), timeCost)
		} else {
			utils.Infof(op.ctx, "read_row||sqlString=%v||valueArgs=%v||proc_time=%v", sqlString, string(valueArgsByte), timeCost)
		}
	}()
	rows, err := op.db.Query(sqlString, valueArgs...)

	if err != nil {
		utils.Error(op.ctx, "Query error:", err)
		return
	}

	defer func() {
		err1 := rows.Close()
		if err1 != nil {
			// 只记录不阻塞
			utils.Error(op.ctx, "rows close error:", err1)
		}
	}()

	row := sqlx.NewRow(rows, nil)
	err = row.StructScan(res)
	if err != nil {
		utils.Error(op.ctx, "StructScan error:", err)
		return err
	}

	return
}

func (op *Operate) writeByTransaction(tx *Tx, sqlString string, valueArgs []interface{}) (affected int64, err error) {
	timeBegin := time.Now().UnixNano()
	defer func() {
		timeEnd := time.Now().UnixNano()
		timeCost := float64(timeEnd-timeBegin) / 1e9 * 1000
		if err != nil {
			utils.Errorf(op.ctx, "writeByTransaction_fail||sqlString=%v||valueArgs=%v||rowsAffected=%v||err=%v||proc_time=%v", sqlString, valueArgs, affected, err.Error(), timeCost)
		} else {
			utils.Infof(op.ctx, "writeByTransaction||sqlString=%v||valueArgs=%v||rowsAffected=%v||proc_time=%v", sqlString, valueArgs, affected, timeCost)
		}
	}()
	stmt, err := tx.Prepare(sqlString)
	if err != nil {
		return affected, err
	}
	defer stmt.Close()
	res, err := stmt.Exec(valueArgs...)
	if err != nil {
		return affected, err
	}
	affected, err = res.RowsAffected()
	if err != nil {
		return affected, err
	}

	if affected < 1 {
		return affected, errors.New("fail to operate order: RowsAffected is not 1")
	}

	return affected, nil
}

func (op *Operate) Insert(sqlString string, valueArgs []interface{}) (affected int64, err error) {
	return op.write(sqlString, valueArgs)
}

func (op *Operate) Delete(sqlString string, valueArgs []interface{}) (affected int64, err error) {
	return op.write(sqlString, valueArgs)
}

func (op *Operate) Update(sqlString string, valueArgs []interface{}) (affected int64, err error) {
	return op.write(sqlString, valueArgs)
}

func (op *Operate) Query(res interface{}, sqlString string, valueArgs []interface{}) (err error) {
	return op.readAll(res, sqlString, valueArgs)
}

func (op *Operate) QueryRow(res interface{}, sqlString string, valueArgs []interface{}) (err error) {
	return op.readRow(res, sqlString, valueArgs)
}

func (op *Operate) InsertByTransaction(tx *Tx, sqlString string, valueArgs []interface{}) (affected int64, err error) {
	return op.writeByTransaction(tx, sqlString, valueArgs)
}

func (op *Operate) UpdateByTransaction(tx *Tx, sqlString string, valueArgs []interface{}) (affected int64, err error) {
	return op.writeByTransaction(tx, sqlString, valueArgs)
}
